package org.dromara.common.excel.convert;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.ExcelEnumFormat;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：枚举格式化转换处理
 */
/**
 * 该类实现了Converter接口，用于在枚举类型和它们的数据库表示（通常是整数值）之间进行转换
 * 主要用于处理Excel文件中枚举类型的转换需求
 */
@Slf4j
public class ExcelEnumConvert implements Converter<Object> {

    /**
     * 返回该组件支持的Java类型键
     *
     * @return 当前组件支持的Java类型键，这里返回Object.class，表示支持所有Java类型
     */
    @Override
    public Class<Object> supportJavaTypeKey() {
        return Object.class;
    }

    /**
     * 返回该组件支持的Excel类型键
     *
     * @return 当前组件支持的Excel类型键，这里返回null，表示没有特定的Excel类型支持
     */
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return null;
    }

    /**
     * 将Excel单元格数据转换为Java应用程序中的数据。
     *
     * 此方法处理从Excel读取的单元格数据，并根据单元格的类型（字符串、数字、布尔值等）
     * 将其转换为适当的Java数据类型。如果单元格为空或值为null，则返回null。
     * 对于枚举类型的转换，使用了预先定义的枚举代码到文本的映射，该映射通过检查
     * Excel单元格中的描述来确定相应的枚举值。
     *
     * @param cellData 单元格数据，包含从Excel读取的值和类型信息。
     * @param contentProperty Excel内容属性，包含字段类型等信息。
     * @param globalConfiguration 全局配置信息，可能影响转换过程。
     * @return 转换后的Java数据对象，或者在单元格为空时返回null。
     * @throws IllegalArgumentException 如果单元格类型不支持或无法处理。
     */
    @Override
    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        // 检查单元格是否为空
        cellData.checkEmpty();
        // Excel中填入的是枚举中指定的描述
        Object textValue = switch (cellData.getType()) {
            case STRING, DIRECT_STRING, RICH_TEXT_STRING -> cellData.getStringValue();
            case NUMBER -> cellData.getNumberValue();
            case BOOLEAN -> cellData.getBooleanValue();
            default -> throw new IllegalArgumentException("单元格类型异常!");
        };
        // 如果是空值
        if (ObjectUtil.isNull(textValue)) {
            return null;
        }
        // 获取枚举代码到文本的映射，用于后续的转换
        Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
        // 从Java输出至Excel是code转text
        // 因此从Excel转Java应该将text与code对调
        // 准备一个反向映射，将文本映射回枚举代码
        Map<Object, Object> enumTextToCodeMap = new HashMap<>();
        // 填充反向映射
        enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key));
        // 应该从text -> code中查找
        // 从反向映射中查找对应的枚举代码
        Object codeValue = enumTextToCodeMap.get(textValue);
        // 最终转换为相应的Java类型并返回
        return Convert.convert(contentProperty.getField().getType(), codeValue);
    }

    /**
     * 将对象转换为Excel单元格数据
     * 此方法用于将枚举类型对象转换为其对应的字符串值，以便在Excel中显示
     *
     * @param object 要转换的枚举对象
     * @param contentProperty Excel内容属性，可能包含枚举值的映射信息
     * @param globalConfiguration 全局配置，可能影响转换过程
     * @return 返回包含要写入Excel单元格的数据对象
     */
    @Override
    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        // 检查对象是否为空，为空则返回空字符串的单元格数据
        if (ObjectUtil.isNull(object)) {
            return new WriteCellData<>("");
        }
        // 在转换前准备，从contentProperty中获取枚举值映射
        Map<Object, String> enumValueMap = beforeConvert(contentProperty);
        // 使用转换工具将枚举对象转换为字符串，如果转换失败或结果为空，则默认为空字符串
        String value = Convert.toStr(enumValueMap.get(object), "");
        // 返回包含转换后字符串值的单元格数据对象
        return new WriteCellData<>(value);
    }

    /**
     * 在转换为Excel内容属性之前进行处理，将枚举值转换为指定的键值对格式
     * 主要用于处理Excel单元格内容的枚举格式注解
     *
     * @param contentProperty Excel内容属性，包含字段信息以及相关的Excel注解信息
     * @return 返回一个映射，其中键是枚举的代码值，值是枚举的文本值
     */
    private Map<Object, String> beforeConvert(ExcelContentProperty contentProperty) {
        // 获取字段上的ExcelEnumFormat注解
        ExcelEnumFormat anno = getAnnotation(contentProperty.getField());
        // 初始化一个HashMap来存储枚举代码和文本的映射
        Map<Object, String> enumValueMap = new HashMap<>();
        // 获取注解中指定的枚举类的所有枚举常量
        Enum<?>[] enumConstants = anno.enumClass().getEnumConstants();
        // 遍历枚举常量，提取每个枚举的代码和文本值，并存入映射中
        for (Enum<?> enumConstant : enumConstants) {
            Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
            String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField());
            enumValueMap.put(codeValue, textValue);
        }
        // 返回处理后的枚举值映射
        return enumValueMap;
    }

    /**
     * 获取字段上的ExcelEnumFormat注解
     *
     * @param field 需要获取注解的字段
     * @return 返回字段上的ExcelEnumFormat注解，如果没有标注该注解则返回null
     */
    private ExcelEnumFormat getAnnotation(Field field) {
        // 使用AnnotationUtil工具类获取字段上的ExcelEnumFormat注解
        return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class);
    }
}
